
                <html lang="en" class="simpread-font simpread-theme-root" style='undefined'>
                    <head>
                        <meta charset="utf-8">
                        <meta http-equiv="content-type" content="text/html; charset=UTF-8;charset=utf-8">
                        <meta http-equiv="X-UA-Compatible" content="IE=Edge">
                        <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=1">
                        <meta name="author" content="Kenshin"/>
                        <meta name="description" content="简悦 SimpRead - 如杂志般沉浸式阅读体验的扩展" />
                        <meta name="keywords" content="Chrome extension, Chrome 扩展, 阅读模式, 沉浸式阅读, 简悦, 简阅, read mode, reading mode, reader view, firefox, firefox addon, userscript, safari, opera, tampermonkey"/>
                        <meta name="thumbnail" content="https://simpread-1254315611.cos.ap-shanghai.myqcloud.com/static/introduce-2.png"/>
                        <meta property="og:title" content="简悦 SimpRead - 如杂志般沉浸式阅读体验的扩展"/>
                        <meta property="og:type" content="website">
                        <meta property="og:local" content="zh_CN"/>
                        <meta property="og:url" content="http://ksria.com/simpread"/>
                        <meta property="og:image" content="https://simpread-1254315611.cos.ap-shanghai.myqcloud.com/static/introduce-2.png"/>
                        <meta property="og:image:type" content="image/png"/>
                        <meta property="og:image:width" content="960"/>
                        <meta property="og:image:height" content="355"/>
                        <meta property="og:site_name" content="http://ksria.com/simpread"/>
                        <meta property="og:description" content="简悦 SimpRead - 如杂志般沉浸式阅读体验的扩展"/>
                        <style type="text/css">.simpread-font{font:300 16px/1.8 -apple-system,PingFang SC,Microsoft Yahei,Lantinghei SC,Hiragino Sans GB,Microsoft Sans Serif,WenQuanYi Micro Hei,sans-serif;color:#333;text-rendering:optimizelegibility;-webkit-text-size-adjust:100%;-webkit-font-smoothing:antialiased}.simpread-hidden{display:none}.simpread-read-root{display:-webkit-flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;position:relative;margin:0;top:-1000px;left:0;width:100%;z-index:2147483646;overflow-x:hidden;opacity:0;-webkit-transition:all 1s cubic-bezier(.23,1,.32,1) .1s;transition:all 1s cubic-bezier(.23,1,.32,1) .1s}.simpread-read-root-show{top:0}.simpread-read-root-hide{top:1000px}sr-read{display:-webkit-flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-flow:column nowrap;flex-flow:column;margin:20px 20%;min-width:400px;min-height:400px;text-align:center}read-process{position:fixed;top:0;left:0;height:3px;width:100%;background-color:#64b5f6;-webkit-transition:width 2s;transition:width 2s;z-index:20000}sr-rd-content-error{display:block;position:relative;margin:0;margin-bottom:30px;padding:25px;background-color:rgba(0,0,0,.05)}sr-rd-footer{-webkit-box-orient:vertical;-ms-flex-direction:column;flex-direction:column;font-size:14px}sr-rd-footer,sr-rd-footer-group{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-direction:normal}sr-rd-footer-group{-webkit-box-orient:horizontal;-ms-flex-direction:row;flex-direction:row;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center}sr-rd-footer-line{width:100%;border-top:1px solid #e0e0e0}sr-rd-footer-text{min-width:150px}sr-rd-footer-copywrite{margin:10px 0 0;color:inherit}sr-rd-footer-copywrite abbr{-webkit-font-feature-settings:normal;font-feature-settings:normal;font-variant:normal;text-decoration:none}sr-rd-footer-copywrite .second{margin:10px 0}sr-rd-footer-copywrite .third a:hover{border:none!important}sr-rd-footer-copywrite .third a:first-child{margin-right:50px}sr-rd-footer-copywrite .sr-icon{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;width:33px;height:33px;opacity:.8;-webkit-transition:opacity .5s ease;transition:opacity .5s ease;cursor:pointer}sr-rd-footer-copywrite .sr-icon:hover{opacity:1}sr-rd-footer-copywrite a,sr-rd-footer-copywrite a:link,sr-rd-footer-copywrite a:visited{margin:0;padding:0;color:inherit;background-color:transparent;font-size:inherit!important;line-height:normal;text-decoration:none;vertical-align:baseline;vertical-align:initial;border:none!important;box-sizing:border-box}sr-rd-footer-copywrite a:focus,sr-rd-footer-copywrite a:hover,sr-rd-footer a:active{color:inherit;text-decoration:none;border-bottom:1px dotted!important}.simpread-blocks{text-decoration:none!important}.simpread-blocks *{margin:0}.simpread-blocks a{padding:0;text-decoration:none!important}.simpread-blocks img{margin:0;padding:0;border:0;background:transparent;box-shadow:none}.simpread-focus-root{display:block;position:fixed;top:0;left:0;right:0;bottom:0;background-color:hsla(0,0%,92%,.9);z-index:2147483645;opacity:0;-webkit-transition:opacity 1s cubic-bezier(.23,1,.32,1) 0ms;transition:opacity 1s cubic-bezier(.23,1,.32,1) 0ms}.simpread-focus-highlight{position:relative;box-shadow:0 0 0 20px #fff;background-color:#fff;overflow:visible;z-index:2147483646}.sr-controlbar-bg sr-rd-crlbar,.sr-controlbar-bg sr-rd-crlbar fab{z-index:2147483647}sr-rd-crlbar.controlbar{position:fixed;right:0;bottom:0;width:100px;height:100%;opacity:0;-webkit-transition:opacity .5s ease;transition:opacity .5s ease}sr-rd-crlbar.controlbar:hover{opacity:1}sr-rd-crlbar fap *{box-sizing:border-box}@media (max-height:620px){fab{zoom:.8}}@media (max-height:783px){dialog-gp dialog-content{max-height:580px}dialog-gp dialog-footer{border-top:1px solid #e0e0e0}}.simpread-highlight-selector{outline:3px dashed #1976d2!important;cursor:pointer!important}.simpread-highlight-controlbar,.simpread-highlight-selector{background-color:#fafafa!important;opacity:.8!important;-webkit-transition:opacity .5s ease!important;transition:opacity .5s ease!important}.simpread-highlight-controlbar{position:relative!important;border:3px dashed #1976d2!important}simpread-highlight,sr-snapshot-ctlbar{position:fixed;top:0;left:0;right:0;padding:15px;height:50px;background-color:rgba(50,50,50,.9);box-shadow:0 2px 5px rgba(0,0,0,.26);box-sizing:border-box;z-index:2147483640}simpread-highlight,sr-highlight-ctl,sr-snapshot-ctlbar{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center}sr-highlight-ctl{margin:0 5px;width:50px;height:20px;color:#fff;background-color:#1976d2;border-radius:4px;box-shadow:0 3px 1px -2px rgba(0,0,0,.2),0 2px 2px 0 rgba(0,0,0,.14),0 1px 5px 0 rgba(0,0,0,.12);cursor:pointer}toc-bg{position:fixed;left:0;top:0;width:50px;height:200px;font-size:medium}toc-bg:hover{z-index:3}.toc-bg-hidden{opacity:0;-webkit-transition:opacity .5s ease;transition:opacity .5s ease}.toc-bg-hidden:hover{opacity:1;z-index:3}.toc-bg-hidden:hover toc{width:180px}toc *{all:unset}toc{position:fixed;left:0;top:100px;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start;padding:10px;width:0;max-width:200px;max-height:500px;overflow-x:hidden;overflow-y:hidden;cursor:pointer;border:1px solid hsla(0,0%,62%,.22);-webkit-transition:width .5s;transition:width .5s}toc:hover{overflow-y:auto}toc.mini:hover{width:200px!important}toc::-webkit-scrollbar{width:3px}toc::-webkit-scrollbar-thumb{border-radius:10px;background-color:hsla(36,2%,54%,.5)}toc outline{position:relative;display:-webkit-box;-webkit-line-clamp:1;-webkit-box-orient:vertical;overflow:hidden;text-overflow:ellipsis;padding:2px 0;min-height:21px;line-height:21px;text-align:left}toc outline a,toc outline a:active,toc outline a:focus,toc outline a:visited{display:block;width:100%;color:inherit;font-size:11px;text-decoration:none!important;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}toc outline a:hover{font-weight:700!important}toc outline a.toc-outline-theme-dark,toc outline a.toc-outline-theme-night{color:#fff!important}.toc-level-h1{padding-left:5px}.toc-level-h2{padding-left:15px}.toc-level-h3{padding-left:25px}.toc-level-h4{padding-left:35px}.toc-outline-active{border-left:2px solid #f44336}toc outline active{position:absolute;left:0;top:0;bottom:0;padding:0 0 0 3px;border-left:2px solid #e8e8e8}sr-kbd{background:-webkit-gradient(linear,0 0,0 100%,from(#fff785),to(#ffc542));border:1px solid #e3be23;-o-border-image:none;border-image:none;-o-border-image:initial;border-image:initial;position:absolute;left:0;padding:1px 3px 0;font-size:11px!important;font-weight:700;box-shadow:0 3px 7px 0 rgba(0,0,0,.3);overflow:hidden;border-radius:3px}.sr-kbd-a{position:relative}kbd-mapping{position:fixed;left:5px;bottom:5px;-ms-flex-flow:row;flex-flow:row;width:250px;height:500px;background-color:#fff;border:1px solid hsla(0,0%,62%,.22);box-shadow:0 2px 5px rgba(0,0,0,.26);border-radius:3px}kbd-mapping,kbd-maps{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}kbd-maps{margin:40px 0 20px;width:100%;overflow-x:auto}kbd-maps::-webkit-scrollbar-thumb{background-clip:padding-box;border-radius:10px;border:2px solid transparent;background-color:rgba(85,85,85,.55)}kbd-maps::-webkit-scrollbar{width:10px;-webkit-transition:width .7s cubic-bezier(.4,0,.2,1);transition:width .7s cubic-bezier(.4,0,.2,1)}kbd-mapping kbd-map-title{position:absolute;margin:5px 0;width:100%;font-size:14px;font-weight:700}kbd-maps-group{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start}kbd-maps-title{margin:5px 0;padding-left:53px;font-size:12px;font-weight:700}kbd-map kbd{display:inline-block;padding:3px 5px;font-size:11px;line-height:10px;color:#444d56;vertical-align:middle;background-color:#fafbfc;border:1px solid #c6cbd1;border-bottom-color:#959da5;border-radius:3px;box-shadow:inset 0 -1px 0 #959da5}kbd-map kbd-name{display:inline-block;text-align:right;width:50px}kbd-map kbd-desc{padding-left:3px}sharecard-bg{position:fixed;top:0;left:0;width:100%;height:100%;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;background-color:rgba(0,0,0,.4);z-index:2147483647}sharecard{max-width:450px;background-color:#64b5f6}sharecard,sharecard-head{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}sharecard-head{margin:25px;color:#fff;border-radius:10px;box-shadow:0 2px 6px 0 rgba(0,0,0,.2),0 25px 50px 0 rgba(0,0,0,.15)}sharecard-card{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}sharecard-card,sharecard-top{display:-webkit-box;display:-ms-flexbox;display:flex}sharecard-top{-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;padding-right:5px;height:65px;background-color:#fff;color:#878787;font-size:25px;font-weight:500;border-top-left-radius:10px;border-top-right-radius:10px}sharecard-top span.logos{display:block;width:48px;height:48px;margin:5px;background-repeat:no-repeat;background-position:50%;background-image:url("");zoom:.8}sharecard-content{padding:15px;max-height:500px;font-size:20px;text-align:justify;background-color:#2196f3;overflow-x:hidden;overflow-y:auto}sharecard-via{padding:10px;font-size:10px;background-color:#2196f3}sharecard-footer{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;padding-right:5px;height:100px;background-color:#fff;color:#878787;font-size:15px;font-weight:500;border-bottom-left-radius:10px;border-bottom-right-radius:10px}sharecard-footer,sharecard-footer div{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}sharecard-footer span.qrcode{display:block;width:100px;height:100px;margin:5px;background-repeat:no-repeat;background-position:50%;background-image:url("")}sharecard-control{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-webkit-box-align:center;-ms-flex-align:center;align-items:center;padding:0 19px;height:80px;background-color:#fff}simpread-snapshot{width:100%;height:100%;cursor:move;z-index:2147483645}simpread-snapshot,sr-mask{position:fixed;left:0;top:0}sr-mask{background-color:rgba(0,0,0,.1)}.simpread-feedback,.simpread-urlscheme{position:fixed;right:20px;bottom:20px;z-index:2147483646}simpread-feedback,simpread-urlscheme{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;padding:20px 20px 0;width:500px;color:rgba(51,51,51,.87);background-color:#fff;border-radius:3px;box-shadow:0 0 2px rgba(0,0,0,.12),0 2px 2px rgba(0,0,0,.26);overflow:hidden;-webkit-transform-origin:bottom;transform-origin:bottom;-webkit-transition:all .6s ease;transition:all .6s ease}simpread-feedback *,simpread-urlscheme *{font-size:12px!important;box-sizing:border-box}simpread-feedback.active,simpread-urlscheme.active{-webkit-animation-name:srFadeInUp;animation-name:srFadeInUp;-webkit-animation-duration:.45s;animation-duration:.45s;-webkit-animation-fill-mode:both;animation-fill-mode:both}simpread-feedback.hide,simpread-urlscheme.hide{-webkit-animation-name:srFadeInDown;animation-name:srFadeInDown;-webkit-animation-duration:.45s;animation-duration:.45s;-webkit-animation-fill-mode:both;animation-fill-mode:both}simpread-feedback sr-fb-label,simpread-urlscheme sr-urls-label{width:100%}simpread-feedback sr-fb-head,simpread-urlscheme sr-urls-head{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;margin-bottom:5px;width:100%}simpread-feedback sr-fb-content,simpread-urlscheme sr-urls-content{margin-bottom:5px;width:100%}simpread-feedback sr-urls-footer,simpread-urlscheme sr-urls-footer{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end;width:100%}simpread-feedback sr-fb-a,simpread-urlscheme sr-urls-a{color:#2163f7;cursor:pointer}simpread-feedback text-field-state,simpread-urlscheme text-field-state{border-top:none rgba(34,101,247,.8)!important;border-left:none rgba(34,101,247,.8)!important;border-right:none rgba(34,101,247,.8)!important;border-bottom:2px solid rgba(34,101,247,.8)!important}simpread-feedback switch,simpread-urlscheme switch{margin-top:0!important}@-webkit-keyframes srFadeInUp{0%{opacity:0;-webkit-transform:translateY(100px);transform:translateY(100px)}to{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}@keyframes srFadeInUp{0%{opacity:0;-webkit-transform:translateY(100px);transform:translateY(100px)}to{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}@-webkit-keyframes srFadeInDown{0%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}to{opacity:0;-webkit-transform:translateY(100px);transform:translateY(100px)}}@keyframes srFadeInDown{0%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}to{opacity:0;-webkit-transform:translateY(100px);transform:translateY(100px)}}simpread-feedback sr-fb-head{font-weight:700}simpread-feedback sr-fb-content{-webkit-box-orient:vertical;-ms-flex-direction:column;flex-direction:column}simpread-feedback sr-fb-content,simpread-feedback sr-fb-footer{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-direction:normal}simpread-feedback sr-fb-footer{-webkit-box-orient:horizontal;-ms-flex-direction:row;flex-direction:row;-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end;width:100%}simpread-feedback sr-close{position:absolute;right:20px;cursor:pointer;-webkit-transition:all 1s cubic-bezier(.23,1,.32,1) .1s;transition:all 1s cubic-bezier(.23,1,.32,1) .1s;z-index:200}simpread-feedback sr-close:hover{-webkit-transform:rotate(-15deg) scale(1.3);transform:rotate(-15deg) scale(1.3)}simpread-feedback sr-stars{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;margin-top:10px}simpread-feedback sr-stars i{margin-right:10px;cursor:pointer}simpread-feedback sr-stars i svg{-webkit-transition:all 1s cubic-bezier(.23,1,.32,1) .1s;transition:all 1s cubic-bezier(.23,1,.32,1) .1s}simpread-feedback sr-stars i svg:hover{-webkit-transform:rotate(-15deg) scale(1.3);transform:rotate(-15deg) scale(1.3)}simpread-feedback sr-stars i.active svg{-webkit-transform:rotate(0) scale(1);transform:rotate(0) scale(1)}simpread-feedback sr-emojis{display:block;height:100px;overflow:hidden}simpread-feedback sr-emoji{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-transition:.3s;transition:.3s}simpread-feedback sr-emoji>svg{margin:15px 0;width:70px;height:70px;-ms-flex-negative:0;flex-shrink:0}simpread-feedback sr-stars-footer{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;margin:10px 0 20px}</style>
                        <style type="text/css">.simpread-theme-root{font-size:62.5%!important}sr-rd-content,sr-rd-desc,sr-rd-title{width:100%}sr-rd-title{display:-webkit-box;margin:1em 0 .5em;overflow:hidden;text-overflow:ellipsis;text-rendering:optimizelegibility;-webkit-line-clamp:3;-webkit-box-orient:vertical}sr-rd-content{text-align:left;word-break:break-word}sr-rd-desc{text-align:justify;line-height:2.4;margin:0 0 1.2em;box-sizing:border-box}sr-rd-content{font-size:25.6px;font-size:1.6rem;line-height:1.6}sr-rd-content h1,sr-rd-content h1 *,sr-rd-content h2,sr-rd-content h2 *,sr-rd-content h3,sr-rd-content h3 *,sr-rd-content h4,sr-rd-content h4 *,sr-rd-content h5,sr-rd-content h5 *,sr-rd-content h6,sr-rd-content h6 *{word-break:break-all}sr-rd-content div,sr-rd-content p{display:block;float:inherit;line-height:1.6;font-size:25.6px;font-size:1.6rem}sr-rd-content div,sr-rd-content p,sr-rd-content pre,sr-rd-content sr-blockquote{margin:0 0 1.2em;word-break:break-word}sr-rd-content a{padding:0 5px;vertical-align:baseline;vertical-align:initial}sr-rd-content a,sr-rd-content a:link{color:inherit;font-size:inherit;font-weight:inherit;border:none}sr-rd-content a:hover{background:transparent}sr-rd-content img{margin:10px;padding:5px;max-width:100%;background:#fff;border:1px solid #bbb;box-shadow:1px 1px 3px #d4d4d4}sr-rd-content figcaption{text-align:center;font-size:14px}sr-rd-content sr-blockquote{display:block;position:relative;padding:15px 25px;text-align:left;line-height:inherit}sr-rd-content sr-blockquote:before{position:absolute}sr-rd-content sr-blockquote *{margin:0;font-size:inherit}sr-rd-content table{width:100%;margin:0 0 1.2em;word-break:keep-all;word-break:normal;overflow:auto;border:none}sr-rd-content table td,sr-rd-content table th{border:none}sr-rd-content ul{margin:0 0 1.2em;margin-left:1.3em;padding:0;list-style:disc}sr-rd-content ol{list-style:decimal;margin:0;padding:0}sr-rd-content ol li,sr-rd-content ul li{font-size:inherit;list-style:disc;margin:0 0 1.2em}sr-rd-content ol li{list-style:decimal;margin-left:1.3em}sr-rd-content ol li *,sr-rd-content ul li *{margin:0;text-align:left;text-align:initial}sr-rd-content li ol,sr-rd-content li ul{margin-bottom:.8em;margin-left:2em}sr-rd-content li ul{list-style:circle}sr-rd-content pre{font-family:Consolas,Monaco,Andale Mono,Source Code Pro,Liberation Mono,Courier,monospace;display:block;padding:15px;line-height:1.5;word-break:break-all;word-wrap:break-word;white-space:pre;overflow:auto}sr-rd-content pre,sr-rd-content pre *,sr-rd-content pre div{font-size:17.6px;font-size:1.1rem}sr-rd-content li pre code,sr-rd-content p pre code,sr-rd-content pre{background-color:transparent;border:none}sr-rd-content pre code{margin:0;padding:0}sr-rd-content pre code,sr-rd-content pre code *{font-size:17.6px;font-size:1.1rem}sr-rd-content pre p{margin:0;padding:0;color:inherit;font-size:inherit;line-height:inherit}sr-rd-content li code,sr-rd-content p code{margin:0 4px;padding:2px 4px;font-size:17.6px;font-size:1.1rem}sr-rd-content mark{margin:0 5px;padding:2px;background:#fffdd1;border-bottom:1px solid #ffedce}.sr-rd-content-img{width:90%;height:auto}.sr-rd-content-img-load{width:48px;height:48px;margin:0;padding:0;border-style:none;border-width:0;background-repeat:no-repeat;background-image:url()}.sr-rd-content-center{text-align:center;display:-webkit-box;-webkit-box-align:center;-webkit-box-pack:center;-webkit-box-orient:vertical}.sr-rd-content-center-small{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex}.sr-rd-content-center-small img{margin:0;padding:0;border:0;box-shadow:none}img.simpread-img-broken{cursor:pointer}.sr-rd-content-nobeautify{margin:0;padding:0;border:0;box-shadow:0 0 0}sr-rd-mult{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;margin:0 0 16px;padding:16px 0 24px;width:100%;background-color:#fff;border-radius:4px;box-shadow:0 1px 2px 0 rgba(60,64,67,.3),0 2px 6px 2px rgba(60,64,67,.15)}sr-rd-mult:hover{-webkit-transition:all .45s 0ms;transition:all .45s 0ms;box-shadow:1px 1px 8px rgba(0,0,0,.16)}sr-rd-mult sr-rd-mult-content{padding:0 16px;overflow:auto}sr-rd-mult sr-rd-mult-avatar,sr-rd-mult sr-rd-mult-content{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}sr-rd-mult sr-rd-mult-avatar{-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin:0 15px}sr-rd-mult sr-rd-mult-avatar span{display:-webkit-box;-webkit-line-clamp:1;-webkit-box-orient:vertical;max-width:75px;overflow:hidden;text-overflow:ellipsis;text-align:left;font-size:16px;font-size:1rem}sr-rd-mult sr-rd-mult-avatar img{margin-bottom:0;max-width:50px;max-height:50px;width:50px;height:50px;border-radius:50%}sr-rd-mult sr-rd-mult-content img{max-width:80%}sr-rd-mult sr-rd-mult-avatar .sr-rd-content-center{margin:0}sr-page{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;width:100%}</style>
                        <style type="text/css">sr-rd-theme-github{display:none}sr-rd-content h1,sr-rd-content h2,sr-rd-content h3,sr-rd-content h4,sr-rd-content h5,sr-rd-content h6{position:relative;margin-top:1em;margin-bottom:1pc;font-weight:700;line-height:1.4;text-align:left;color:#363636}sr-rd-content h1{padding-bottom:.3em;font-size:57.6px;font-size:3.6rem;line-height:1.2}sr-rd-content h2{padding-bottom:.3em;font-size:44.8px;font-size:2.8rem;line-height:1.225}sr-rd-content h3{font-size:38.4px;font-size:2.4rem;line-height:1.43}sr-rd-content h4{font-size:32px;font-size:2rem}sr-rd-content h5,sr-rd-content h6{font-size:25.6px;font-size:1.6rem}sr-rd-content h6{color:#777}sr-rd-content ol,sr-rd-content ul{list-style-type:disc;padding:0;padding-left:2em}sr-rd-content ol ol,sr-rd-content ul ol{list-style-type:lower-roman}sr-rd-content ol ol ol,sr-rd-content ol ul ol,sr-rd-content ul ol ol,sr-rd-content ul ul ol{list-style-type:lower-alpha}sr-rd-content table{width:100%;overflow:auto;word-break:normal;word-break:keep-all}sr-rd-content table th{font-weight:700}sr-rd-content table td,sr-rd-content table th{padding:6px 13px;border:1px solid #ddd}sr-rd-content table tr{background-color:#fff;border-top:1px solid #ccc}sr-rd-content table tr:nth-child(2n){background-color:#f8f8f8}sr-rd-content sr-blockquote{border-left:4px solid #ddd}.simpread-theme-root{background-color:#fff;color:#333}sr-rd-title{font-family:PT Sans,SF UI Display,\.PingFang SC,PingFang SC,Neue Haas Grotesk Text Pro,Arial Nova,Segoe UI,Microsoft YaHei,Microsoft JhengHei,Helvetica Neue,Source Han Sans SC,Noto Sans CJK SC,Source Han Sans CN,Noto Sans SC,Source Han Sans TC,Noto Sans CJK TC,Hiragino Sans GB,sans-serif;font-size:54.4px;font-size:3.4rem;font-weight:700;line-height:1.3}sr-rd-desc{position:relative;margin:0;margin-bottom:30px;padding:25px;padding-left:56px;font-size:28.8px;font-size:1.8rem;color:#777;background-color:rgba(0,0,0,.05);box-sizing:border-box}sr-rd-desc:before{content:"\201C";position:absolute;top:-28px;left:16px;font-size:80px;font-family:Arial;color:rgba(0,0,0,.15)}sr-rd-content,sr-rd-content *,sr-rd-content div,sr-rd-content p{color:#363636;font-weight:400;line-height:1.8}sr-rd-content b *,sr-rd-content strong,sr-rd-content strong * sr-rd-content b{-webkit-animation:none 0s ease 0s 1 normal none running;animation:none 0s ease 0s 1 normal none running;-webkit-backface-visibility:visible;backface-visibility:visible;background:transparent none repeat 0 0/auto auto padding-box border-box scroll;border:medium none currentColor;border-collapse:separate;-o-border-image:none;border-image:none;border-radius:0;border-spacing:0;bottom:auto;box-shadow:none;box-sizing:content-box;caption-side:top;clear:none;clip:auto;color:#000;-webkit-columns:auto;-moz-columns:auto;columns:auto;-webkit-column-count:auto;-moz-column-count:auto;column-count:auto;-webkit-column-fill:balance;-moz-column-fill:balance;column-fill:balance;-webkit-column-gap:normal;-moz-column-gap:normal;column-gap:normal;-webkit-column-rule:medium none currentColor;-moz-column-rule:medium none currentColor;column-rule:medium none currentColor;-webkit-column-span:1;-moz-column-span:1;column-span:1;-webkit-column-width:auto;-moz-column-width:auto;column-width:auto;content:normal;counter-increment:none;counter-reset:none;cursor:auto;direction:ltr;display:inline;empty-cells:show;float:none;font-family:serif;font-size:medium;font-style:normal;font-variant:normal;font-weight:400;font-stretch:normal;line-height:normal;height:auto;-webkit-hyphens:none;-ms-hyphens:none;hyphens:none;left:auto;letter-spacing:normal;list-style:disc outside none;margin:0;max-height:none;max-width:none;min-height:0;min-width:0;opacity:1;orphans:2;outline:medium none invert;overflow:visible;overflow-x:visible;overflow-y:visible;padding:0;page-break-after:auto;page-break-before:auto;page-break-inside:auto;-webkit-perspective:none;perspective:none;-webkit-perspective-origin:50% 50%;perspective-origin:50% 50%;position:static;right:auto;-moz-tab-size:8;-o-tab-size:8;tab-size:8;table-layout:auto;text-align:left;text-align-last:auto;text-decoration:none;text-indent:0;text-shadow:none;text-transform:none;top:auto;-webkit-transform:none;transform:none;-webkit-transform-origin:50% 50% 0;transform-origin:50% 50% 0;-webkit-transform-style:flat;transform-style:flat;-webkit-transition:none 0s ease 0s;transition:none 0s ease 0s;unicode-bidi:normal;vertical-align:baseline;visibility:visible;white-space:normal;widows:2;width:auto;word-spacing:normal;z-index:auto;all:initial}sr-rd-content a,sr-rd-content a:link{color:#4183c4;text-decoration:none}sr-rd-content a:active,sr-rd-content a:focus,sr-rd-content a:hover{color:#4183c4;text-decoration:underline}sr-rd-content pre{background-color:#f7f7f7;border-radius:3px}sr-rd-content li code,sr-rd-content p code{background-color:rgba(0,0,0,.04);border-radius:3px}.simpread-multi-root{background:#f8f9fa}</style>
                        <style type="text/css"></style>
                        <style type="text/css">@media (pointer:coarse){sr-read{margin:20px 5%!important;min-width:0!important;max-width:90%!important}sr-rd-title{margin-top:0;font-size:2.7rem}sr-rd-content sr-blockquote,sr-rd-desc{margin:10 0!important;padding:0 0 0 10px!important;width:90%;font-size:1.8rem;font-style:normal;line-height:1.7;text-align:justify}sr-rd-content{font-size:1.75rem;font-weight:300}sr-rd-content figure{margin:0;padding:0;text-align:center}sr-rd-content a,sr-rd-content a:link,sr-rd-content li code,sr-rd-content p code{font-size:inherit}sr-rd-footer{margin-top:20px}sr-blockquote,sr-blockquote *{margin:5px!important;padding:5px!important}sr-rd-content h1,sr-rd-content h2,sr-rd-content h3,sr-rd-content h4,sr-rd-content h5,sr-rd-content h6,sr-rd-title{font-family:PingFang SC,Verdana,Helvetica Neue,Microsoft Yahei,Hiragino Sans GB,Microsoft Sans Serif,WenQuanYi Micro Hei,sans-serif;color:#000;font-weight:100;line-height:1.35}sr-rd-content-h1,sr-rd-content-h2,sr-rd-content-h3,sr-rd-content-h4,sr-rd-content-h5,sr-rd-content-h6,sr-rd-content h1,sr-rd-content h2,sr-rd-content h3,sr-rd-content h4,sr-rd-content h5,sr-rd-content h6{margin-top:1.2em;margin-bottom:.6em;line-height:1.35}sr-rd-content-h1,sr-rd-content h1{font-size:1.8em}sr-rd-content-h2,sr-rd-content h2{font-size:1.6em}sr-rd-content-h3,sr-rd-content h3{font-size:1.4em}sr-rd-content-h4,sr-rd-content-h5,sr-rd-content-h6,sr-rd-content h4,sr-rd-content h5,sr-rd-content h6{font-size:1.2em}sr-rd-content-ul,sr-rd-content ul{margin-left:1.3em!important;list-style:disc}sr-rd-content-ol,sr-rd-content ol{list-style:decimal;margin-left:1.9em!important}sr-rd-content-ol ol,sr-rd-content-ol ul,sr-rd-content-ul ol,sr-rd-content-ul ul,sr-rd-content li ol,sr-rd-content li ul{margin-bottom:.8em;margin-left:2em!important}sr-rd-content img{margin:0;padding:0;border:0;max-width:100%!important;height:auto;box-shadow:0 20px 20px -10px rgba(0,0,0,.1)}sr-rd-mult{min-width:0;background-color:#fff;box-shadow:0 1px 6px rgba(32,33,36,.28);border-radius:8px}sr-rd-mult sr-rd-mult-avatar div{margin:0}sr-rd-mult sr-rd-mult-avatar .sr-rd-content-center-small{margin:7px 0!important}sr-rd-mult sr-rd-mult-avatar span{display:block}sr-rd-mult sr-rd-mult-content{padding-left:0}@media only screen and (max-device-width:1024px){.simpread-theme-root,html.simpread-theme-root{font-size:80%!important}sr-rd-mult sr-rd-mult-avatar img{width:50px;height:50px;min-width:50px;min-height:50px}toc-bg toc{width:10px!important}toc-bg:hover toc{width:auto!important}}@media only screen and (max-device-width:414px){.simpread-theme-root,html.simpread-theme-root{font-size:70%!important}sr-rd-mult sr-rd-mult-avatar img{width:30px;height:30px;min-width:30px;min-height:30px}}@media only screen and (max-device-width:320px){.simpread-theme-root,html.simpread-theme-root{font-size:90%!important}sr-rd-content p{margin-bottom:.5em}}}</style>
                        <style type="text/css">sr-rd-content *, sr-rd-content p, sr-rd-content div {}sr-rd-content pre code, sr-rd-content pre code * {}sr-rd-desc {}sr-rd-content pre {}sr-rd-title {}</style>
                        <style type="text/css"></style>
                        <style type="text/css"></style>
                        <style type="text/css"></style>
                        <style type="text/css"></style>
                        <style type="text/css">sr-rd-content *, sr-rd-content p, sr-rd-content div {
        font-size: 15px;
    }

    .annote-perview, .annote-perview * {
        color: rgb(85, 85, 85);
        font-weight: 400;
        line-height: 1.8;
    }</style>
                        
                        
                        <script>setTimeout(()=>{const e=location.hash.replace("#id=","");let t,a=!1;const n=t=>{for(let n of t){let t;if((t=e.length>6?n.getAttribute("data-id"):n.getAttribute("data-idx"))==e){n.scrollIntoView({behavior:"smooth",block:"start",inline:"nearest"}),a=!0;break}}};e&&(0==(t=document.getElementsByClassName("sr-unread-card")).length&&(t=document.getElementsByTagName("sr-annote")),n(t),a||n(t=document.getElementsByClassName("sr-annote")))},500);</script>
                        
                        <title>简悦 | socket 编程 | 白月黑羽</title>
                    </head>
                    <body>
                        <sr-read style='undefined'>
                            <sr-rd-title>socket 编程 | 白月黑羽</sr-rd-title>
                    <sr-rd-desc style="margin: 0;padding-top: 0;padding-bottom: 0;font-style: normal;font-size: 18px;">什么是 Socket 编程 点击这里，边看视频讲解，边学习以下内容 白月黑羽写本文的时候是 2019 年，现在的软件开发基本都需要 网络通讯 。 不管是传统计算机软件，</sr-rd-desc>
                    <sr-rd-content><p></p><h2 id="sr-toc-0">什么是 Socket 编程</h2><p></p><p><a href="https://www.bilibili.com/video/av74106411/?p=80" target="_blank">点击这里，边看视频讲解，边学习以下内容</a></p><p></p><p>白月黑羽写本文的时候是 2019 年，现在的软件开发基本都需要 <code>网络通讯</code> 。</p><p></p><p>不管是传统计算机软件，还是手机软件，还是物联网嵌入系统软件，这些都要和其他网络系统进行通讯。</p><p></p><p>而当今网络世界 基本上 都是使用 TCP/IP 协议进行通讯的。</p><p></p><p>任何应用，比如 浏览网页、微信、支付宝、抖音  或者我们开发的 等等都是 通过 TCP/IP 协议进行通讯的。</p><p></p><p>TCP/IP 协议 就是一种传输 数据的 方案。</p><p></p><p>我们可以用 发快递 打比方。</p><p></p><p>在南京的白月 要寄一个 物品 给 在北京的黑羽。</p><p></p><p>选择一个快递公司，就是选择了一种 传输物品的 规范。 因为不同的快递公司 传输物品的具体方案不同。</p><p></p><p>白月 作为一个 寄件人， 他  <code>不需要知道</code>  快递公司  传输物品的方案的 <code>所有细节</code> 。</p><p></p><p>他只要知道 如何把 物品 给快递公司上门的收件人，就行了。</p><p></p><p>黑羽 作为一个收件人， 他也  不需要知道  快递公司  传输物品的方案的所有细节。</p><p></p><p>他只要知道，如何从快递公司的收件人 收物品 就行了。</p><p></p><p></p><p>对应到 软件开发上，</p><p></p><p>收发信息的  <code>程序进程</code>  就像   <code>发件人 和 收件人</code> ；</p><p></p><p>收发的  <code>信息</code>  就像 快递传输的  <code>物品</code> ；</p><p></p><p>具体信息的传输路径（中间经过哪些路由器）和传输的方法（使用什么协议）就像 快递公司的运输流程；</p><p></p><p>同样的，我们编写 发出信息的程序和接收信息的程序，并不需要知道 信息传输的所有细节，比如 中间经过哪些路由器，路由器之间又是如何传输的。</p><p></p><p>我们作为程序员，只要知道，我们的程序如何把  所要发送的信息 交给 ‘收件人’， 如何从 ‘送件人’ 手中获取信息。</p><p></p><p>那么 和我们的 应用程序 直接打交道的 ‘收件人’ 和 ‘送件人’ 到底是谁？</p><p></p><p>就是操作系统 提供的 <code>socket 编程接口</code></p><p></p><p>发送信息的应用程序，通过  <code>socket 编程接口</code> 把信息给操作系统的 TCP/IP 协议栈通讯模块；</p><p></p><p>通讯模块一层层传递给 其他通讯模块（网卡驱动等），最后再通过网卡等硬件设备发送到网络上去；</p><p></p><p>经过 网络上路由器的一次次转发，最终到了 目的程序 所在的 计算机（或者手机等设备） ， 再通过 其 操作系统的 TCP/IP 协议栈通讯模块 一层层上传。</p><p></p><p>最后接收信息的程序，通过 <code>socket 编程接口</code> 接收到了 传输的信息。</p><p></p><p>这个过程可以用下图来表示</p><p></p><p><div class="sr-rd-content-center-small"><img class="simpread-img-broken" src="" org-src="http://cdn1.python3.vip/imgs/socket1.png"></div></p><p></p><p>我们前面使用过 requests 库 发送 HTTP 请求消息，其实 requests 库底层也是使用的 socket 编程接口发送 HTTP 请求消息。</p><p></p><p>HTTP 传输的消息 底层也是通过 TCP/IP 协议 传输的， HTTP 加上了一些额外的规定， 比如传输消息的格式。</p><p></p><p>就像我们发快递的时候做了些额外的处理。比如 把物品 放到一个盒子里。</p><p></p><p></p><h2 id="sr-toc-1">TCP Socket 编程</h2><p></p><p><a href="https://www.bilibili.com/video/av74106411/?p=81" target="_blank">点击这里，边看视频讲解，边学习以下内容</a></p><p></p><p>要进行 socket 编程，发送网络消息，我们可以使用 Python 内置的 socket 库 。</p><p></p><p>目前的 socket 编程，使用的最多的就是通过 TCP 协议进行网络通讯的。</p><p></p><p>TCP 进行通讯的程序双方，分为服务端和客户端。</p><p></p><p>TCP 协议进行通讯的双方，是需要先建立一个虚拟连接的。然后双方程序才能发送业务数据信息。</p><p></p><p>建立 TCP 虚拟连接是通过著名的 <code>三次握手</code> 进行的。</p><p></p><p>具体三次握手的细节大家可以参考这篇文章 <a href="https://zhuanlan.zhihu.com/p/40499563">https://zhuanlan.zhihu.com/p/40499563</a></p><p></p><p>我们现在来看一个 TCP 协议进行通讯的 socket 服务端程序和客户端程序。</p><p></p><p>下面是 TCP 服务端程序 server.py</p><p></p><pre>#  === TCP 服务端程序 server.py ===

# 导入socket 库
from socket import *

# 主机地址为空字符串，表示绑定本机所有网络接口ip地址
# 等待客户端来连接
IP = ''
# 端口号
PORT = 50000
# 定义一次从socket缓冲区最多读入512个字节数据
BUFLEN = 512

# 实例化一个socket对象
# 参数 AF_INET 表示该socket网络层使用IP协议
# 参数 SOCK_STREAM 表示该socket传输层使用TCP协议
listenSocket = socket(AF_INET, SOCK_STREAM)

# socket绑定地址和端口
listenSocket.bind((IP, PORT))


# 使socket处于监听状态，等待客户端的连接请求
# 参数 8 表示 最多接受多少个等待连接的客户端
listenSocket.listen(8)
print(f'服务端启动成功，在{PORT}端口等待客户端连接...')

dataSocket, addr = listenSocket.accept()
print('接受一个客户端连接:', addr)

while True:
    # 尝试读取对方发送的消息
    # BUFLEN 指定从接收缓冲里最多读取多少字节
    recved = dataSocket.recv(BUFLEN)

    # 如果返回空bytes，表示对方关闭了连接
    # 退出循环，结束消息收发
    if not recved:
        break

    # 读取的字节数据是bytes类型，需要解码为字符串
    info = recved.decode()
    print(f'收到对方信息： {info}')

    # 发送的数据类型必须是bytes，所以要编码
    dataSocket.send(f'服务端接收到了信息 {info}'.encode())

# 服务端也调用close()关闭socket
dataSocket.close()
listenSocket.close()

</pre><p></p><p>下面是 TCP 客户端程序 client.py</p><p></p><pre>#  === TCP 客户端程序 client.py ===

from socket import *

IP = '127.0.0.1'
SERVER_PORT = 50000
BUFLEN = 1024

# 实例化一个socket对象，指明协议
dataSocket = socket(AF_INET, SOCK_STREAM)

# 连接服务端socket
dataSocket.connect((IP, SERVER_PORT))

while True:
    # 从终端读入用户输入的字符串
    toSend = input('&gt;&gt;&gt; ')
    if  toSend =='exit':
        break
    # 发送消息，也要编码为 bytes
    dataSocket.send(toSend.encode())

    # 等待接收服务端的消息
    recved = dataSocket.recv(BUFLEN)
    # 如果返回空bytes，表示对方关闭了连接
    if not recved:
        break
    # 打印读取的信息
    print(recved.decode())

dataSocket.close()

</pre><p></p><p>大家保存一下代码运行一遍看看。</p><p></p><p>注意，要先运行服务段，再运行客户段。</p><p></p><p>上面代码的细节，请看 上面链接的视频讲解</p><p></p><h2 id="sr-toc-2">应用消息格式</h2><p></p><h3 id="sr-toc-3">为什么要定义消息格式</h3><p></p><p><a href="https://www.bilibili.com/video/av74106411/?p=82" target="_blank">点击这里，边看视频讲解，边学习以下内容</a></p><p></p><p>上面的例子中，我们发送的消息就是要传递的内容。 比如字符串  <code>你好，我是白月黑羽</code> 。</p><p></p><p>实际上，我们在企业中开发的程序通讯，消息往往是有 <code>格式定义</code> 的。 消息的格式定义可以归入 OSI 网络模型的 <code>表示层</code> 。</p><p></p><p>比如: 定义的消息包括 消息头 和 消息体。</p><p></p><p>消息头存放消息的格式数据， 比如 消息的长度、类型、状态等等， 而消息体存放具体的传送数据。</p><p></p><p>对于使用 TCP 协议传输信息的程序来说，格式定义一定要明确规定  <code>消息的边界</code>  。</p><p></p><p>因为 TCP 协议传输的是  <code>字节流（bytes stream）</code>，  如果消息中没有指定 边界 或者 长度，接收方就不知道一个完整的消息从字节流的 哪里开始，到 哪里结束。</p><p></p><p>具体的讲解，请参看上面链接的视频讲解。</p><p></p><p>指定消息的边界有两种方式：</p><p></p><ul>
<li>用特殊字节作为消息的结尾符号</li>
</ul><p></p><p>可以用消息内容中不可能出现的字节串 （比如  <code>FFFFFF</code>）  作为消息的结尾字符。</p><p></p><ul>
<li>在消息开头某个位置，直接指定消息的长度</li>
</ul><p></p><p>比如在一个消息的最前面用 2 个字节表示本消息的长度。</p><p></p><p></p><div><p>  
UDP 协议通常不需要指定消息边界，因为 UDP 是数据报协议，应用程序从 socket 接收到的必定是发送方发送的完整消息。  
</p></div><p></p><h3 id="sr-toc-4">示例 1</h3><p></p><p><a href="https://www.bilibili.com/video/av85532490?p=4" target="_blank">点击这里，边看视频讲解，边学习以下内容</a></p><p></p><p>我们现在要开发一个实验室的工作站监控系统，包括</p><p></p><ul>
<li>
<p>安装在机房工作站上的 数据采集器 RUS</p>
<p>这个程序作为 TCP 服务端，获取资源使用数据，简称 RUS （Resource Usage Stat）</p>
</li>
<li>
<p>安装在监控室的管理控制台 AT</p>
<p>这个程序作为 TCP 客户端，向管理员显示资源使用数据，简称 AT （Admin Terminal）</p>
</li>
</ul><p></p><p>这是我们白月黑羽实战班的 一个项目实战练习。</p><p></p><p>作为这个系统的设计者，你可以自行设计 RUS 和 AT 之间的数据传输规范，包括消息 数据格式规范。</p><p></p><p>下面是一种参考的规范：</p><p></p><ul>
<li>
<p>AT 和 RUS 之间采用 TCP 长连接方式进行通讯</p>
<p>如果中途出现连接断开，AT 作为 TCP 客户端必须进行重连</p>
</li>
<li>
<p>消息整体</p>
<p>每个消息 都是 UTF8 编码的 字符串</p>
<p>由消息头 和消息体组成。</p>
<p>消息头 和消息体之间 用一个 换行符 （UTF8 编码后的字节为  <code>0A</code> ）隔开。</p>
<p>有如下类型的消息：</p>
<ul>
<li>
<p>控制命令</p>
<p>由 AT 发送给 RUS ， 下达管理控制命令。</p>
<p>比如：</p>
<ul>
<li>pause 暂停数据采集</li>
<li>resume 恢复数据采集</li>
</ul>
<p>RUS 接收到 控制命令后，必须完成操作后必须回复响应消息，告诉 AT 命令已经接收已经完成</p>
</li>
<li>
<p>数据上报</p>
<p>由 RUS 发送给 AT，汇报采集的资源数据。 AT 接收到数据后，应该回复一个接收汇报的响应消息。</p>
</li>
</ul>
</li>
<li>
<p>消息头</p>
<p>消息头只包含一个信息： 消息体的长度</p>
<p>消息头用十进制的字符串 表示一个整数的长度</p>
</li>
<li>
<p>消息体</p>
<p>消息体用 json 格式的字符串 表示数据信息，如下</p>
<ul>
<li>数据上报 RUS -&gt; AT</li>
</ul>
<pre>{
    "type" : "report",
    "info" : {
        "CPU Usage" : "30%",
        "Mem usage" : "53%"
    }
}

</pre>
<ul>
<li>数据上报响应 AT -&gt; RUS</li>
</ul>
<pre>{
    "type" : "report-ack"
}

</pre>
<ul>
<li>暂停数据上报命令 AT -&gt; RUS</li>
</ul>
<pre>{
    "type" : "pause",
    "duration" :  200
}

</pre>
<p>其中 duration 表示暂停上报的时间，以秒为单位</p>
<ul>
<li>恢复数据上报命令 AT -&gt; RUS</li>
</ul>
<pre>{
    "type" : "resume"
}

</pre>
<ul>
<li>命令处理响应 RUS -&gt; AT</li>
</ul>
<pre>{
    "type" : "cmd-ack",
    "code" :  200,
    "info" : "处理成功"
}

</pre>
<p>其中 code 是处理结果码，用 200 表示成功。
info 是处理结果文字描述。</p>
</li>
</ul><p></p><p>实战班学员请联系老师获取详细的实战练习指导。</p><p></p><h3 id="sr-toc-5">示例 2</h3><p></p><p><a href="https://www.bilibili.com/video/av85532490?p=5" target="_blank">点击这里，边看视频讲解，边学习以下内容</a></p><p></p><p>示例 1 中，我们给出的参考 接口， 传递的消息都是 放在一个大字符串里面， 然后采用字符串编码为 字节串进行传输的。</p><p></p><p>这种接口设计的好处就是简单，便于发送时的字节编码操作：消息头和消息体分别进行 UTF8 编码，然后字节串拼接即可</p><p></p><p>接收方处理也简单，直接分离出消息头和消息体，分别进行 UTF8 解码即可。</p><p></p><p>我们设计普通应用程序之间的通信，这样就很好，简单就是美，容易开发，容易维护。</p><p></p><p>但是如果消息接口是在  <code>秒理万机</code> 的计算节点之间的通讯， 这样的接口的缺点就暴露了：消息长，而且编解码耗费处理器资源比较大。</p><p></p><p>典型的例子，就是通讯设备， 比如 4G 核心网的业务处理节点。 它们每秒往往要处理数以万计的认证、鉴权、计费 等消息，采用上述方法会给设备带来巨大负担。</p><p></p><p>首先，数据都用字符表示，其实是比较浪费带宽的做法。</p><p></p><p>比如返回码 用 200 这样的字符串表示，就会耗费 3 个字节，24 个比特。 如果处理结果 只有成功和不成功，只需要 1 个 bit 即可， 1 表示成功，0 表示不成功</p><p></p><p>其次， json 这种复杂语法的编解码算法，需要程序代码进行各种复杂处理（参考一下 Python json 内置库的代码）是比较耗费 CPU 资源的。</p><p></p><p>可以定义更为简单的数据表达方式，比如像这样：</p><p></p><ul>
<li>
<p>消息头 开头 2 个字节表示消息的长度</p>
</li>
<li>
<p>消息头 第 3 个字节表示 消息类型 ：</p>
<p>0：暂停命令， 1 ：恢复命令  2：命令响应  3：统计上报 4：统计上报响应</p>
</li>
<li>
<p>消息体 数据定义</p>
<p>可以使用类似 Radius/Diameter Attribute-Value Pairs （AVP） 的定义方法</p>
<p>Attribute： 使用一个字节，表示数据种类。</p>
<p>比如</p>
<p>1： CPU 使用率
2： 内存使用率</p>
<p>Length： 使用一个字节，表示信息长度</p>
<p>Value： 表示具体的数据值</p>
<p>这样，前面的示例信息</p>
<pre> {
        "CPU Usage" : "30%",
        "Mem usage" : "53%"
 }  

</pre>
<p>其中</p>
<p><code>"CPU Usage" : "30%"</code> , 就像这样用 16 进制字节表示  <code>01011E</code></p>
<p><code>"Mem usage" : "53%"</code> , 就像这样用 16 进制字节表示  <code>020135</code></p>
<p>合起来就是 <code>01011E020135</code></p>
</li>
</ul><p></p><p>对比一下，第一种编码方法</p><p></p><p>优点：更节省传输带宽，编码解码数据效率更高</p><p></p><p>缺点：对于人的可读性差，数据表示灵活性较差；</p><p></p><h2 id="sr-toc-6">支持多个 TCP 客户端</h2><p></p><p>上面的服务端代码 只能和一个客户端进行通信。</p><p></p><p>如果我们同时运行多个客户端，就会发现 后面的客户端程序不能和服务端连接成功。为什么呢？</p><p></p><p>因为，服务端程序必须不停的对 监听 socket 对象调用 accept() 方法，才能不断的接受 新的客户端连接请求。</p><p></p><p>而且 还需要运行额外的代码 对 多个客户端连接后，返回的多个数据传输 socket 对象 进行数据的收发。</p><p></p><p>显然，我们上面的程序没有这样的处理。</p><p></p><p>因为缺省情况创建的 socket 是 <code>阻塞式</code> 的，进行 accpet 调用时，如果没有客户端连接，程序就阻塞在此处，不再执行后续代码。</p><p></p><p>同样的，调用 recv 方法，如果没有数据在本 socket 的接收缓冲，也会阻塞。</p><p></p><p>所以，通常一个线程里面，没法 不断地 调用 监听 socket 的 accept 方法，同时还能 负责多个 数据传输 socket 消息的收发。</p><p></p><p>那么让一个服务端程序 和多个客户端同时连接 并 通信 呢？</p><p></p><p>聪明的你一定想到了，一个线程不行，就使用多个线程啊。</p><p></p><p>我们 修改服务端的代码，如下</p><p></p><pre>#  === TCP 服务端程序 server.py ， 支持多客户端 ===

# 导入socket 库
from socket import *
from threading import Thread

IP = ''
PORT = 50000
BUFLEN = 512

# 这是新线程执行的函数，每个线程负责和一个客户端进行通信
def clientHandler(dataSocket,addr):
    while True:
        recved = dataSocket.recv(BUFLEN)
        # 当对方关闭连接的时候，返回空字符串
        if not recved:
            print(f'客户端{addr} 关闭了连接' )
            break

        # 读取的字节数据是bytes类型，需要解码为字符串
        info = recved.decode()
        print(f'收到{addr}信息： {info}')

        dataSocket.send(f'服务端接收到了信息 {info}'.encode())

    dataSocket.close()

# 实例化一个socket对象 用来监听客户端连接请求
listenSocket = socket(AF_INET, SOCK_STREAM)

# socket绑定地址和端口
listenSocket.bind((IP, PORT))

listenSocket.listen(8)
print(f'服务端启动成功，在{PORT}端口等待客户端连接...')

while True:
   # 在循环中，一直接受新的连接请求
   dataSocket, addr = listenSocket.accept()     # Establish connection with client.
   addr = str(addr)
   print(f'一个客户端 {addr} 连接成功' )

   # 创建新线程处理和这个客户端的消息收发
   th = Thread(target=clientHandler,args=(dataSocket,addr))
   th.start()

listenSocket.close()

</pre><p></p><p>多线程方式有个缺点。</p><p></p><p>如果一个服务端要同时处理大量的客户端连接，比如 10000 个，需要创建 10000 个线程。</p><p></p><p>而操作系统通常不可能为一个进程分配这么多的线程。</p><p></p><p>实际上，我们的服务端程序，大部分时间都是空闲的，都在等待连接请求，等待接受消息，根本不需要这么多的线程来处理。</p><p></p><p>这种程序通常被称之为 IO bound 程序，也就是说程序的主要时间都是花费在 IO 上面。</p><p></p><p>这种程序，其实一个线程就足够了。</p><p></p><p>关键问题是，需要这一个线程 很好的分配 时间， 在有连接请求到来的时候，执行处理连接请求代码，有消息到达 socket 缓冲的时候，执行读取处理消息的代码。</p><p></p><p>这种处理方式称之为异步 IO。</p><p></p><p>Python 3 新增了 asyncio 库， 我们可以使用该库来 实现 同时处理多个客户端数据收发。</p><p></p><p>示例代码如下：</p><p></p><pre>#  === TCP 服务端程序 server.py 异步支持多客户端 ===
import asyncio, socket
IP = ''
PORT = 50000
BUFLEN = 512

# 定义处理数据收发的回调
async def handle_echo(reader, writer):
    addr = writer.get_extra_info('peername')
    while True:
        data = await reader.read(100)
        if not data:
            print(f'客户端{addr}关闭了连接')
            writer.close()
            break

        message = data.decode()
        print(f'收到{addr}信息： {message}')

        writer.write(data)

loop = asyncio.get_event_loop()
coro = asyncio.start_server(handle_echo, IP, PORT, loop=loop)
server = loop.run_until_complete(coro)

# Serve requests until Ctrl+C is pressed
print('服务端启动成功，在{}端口等待客户端连接...'.format(server.sockets[0].getsockname()[1]))
try:
    loop.run_forever()
except KeyboardInterrupt:
    pass

# Close the server
server.close()
loop.run_until_complete(server.wait_closed())
loop.close()

</pre><p></p><p>&lt;br&gt;&lt;br&gt;

&lt;h4 class="text-center" style='color: #c71111'&gt; 有收获吗？请支持我们做更好的教程 &lt;/h4&gt;

![image](http://cdn1.byhy.net/etc/pay.png)

&lt;br&gt;&lt;br&gt;

&lt;p class="text-center" style='color: #c71111'&gt; 扫码分享给朋友，一起学更有动力哦 &lt;/p&gt;

&lt;div id="code" class="text-center"&gt;&lt;/div&gt;

&lt;br&gt;

&lt;div style="width:100%;text-align:center;" id='ggaos'&gt;
&lt;/div&gt;
 
&lt;br&gt;

&lt;script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"&gt;&lt;/script&gt;
&lt;script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.qrcode/1.0/jquery.qrcode.min.js"&gt;&lt;/script&gt;


&lt;script&gt;
        
    $("#code").qrcode({ 
        //render: "table", //table 方式 
        width: 200, // 宽度 
        height:200, // 高度 
        text: window.location.href 
    });

    if (true){
        let  toAdd1 = `        
&lt;div  id='ggao1'&gt;
    &lt;a href="/adv/vipcourse/"&gt;
        &lt;img src='http://cdn1.python3.vip/imgs/gg03.png' 
            title="点击查看详情" 
            width="540" 
            height="300"/&gt;
    &lt;/a&gt;
    &lt;br&gt;&lt;br&gt;&lt;p class="text-center" style='color: #c71111'&gt; 点击上方图片，查看课程详情 &lt;/p&gt;
    
    &lt;p class="text-center" style='color: #c71111'&gt; 咨询 QQ：1750696907 | 微信：byhy44&lt;/p&gt;
   
&lt;/div&gt;
      
&lt;div  id='ggao2'&gt;
    &lt;a href="/adv/techsupport/"&gt;
        &lt;img src='http://cdn1.python3.vip/imgs/etc/gg04.png' 
            title="点击查看详情" /&gt;
    &lt;/a&gt;
    &lt;br&gt;
   
&lt;/div&gt;
`
        
        let gg = document.getElementById("ggaos");
        gg.insertAdjacentHTML('beforeend',toAdd1);
    }
    
&lt;/script&gt;</p><p></p><h2 id="sr-toc-7">UDP Socket 编程</h2><p></p><h3 id="sr-toc-8">UDP 协议特点</h3><p></p><p>UDP (User Datagram Protocol) 中文称之为 用户数据报协议， 和 TCP 一样，也是一种传输层协议。</p><p></p><p>和 TCP 较大的不同点在于：</p><p></p><ol>
<li>它是一种无连接 协议</li>
</ol><p></p><p>也就是说：无需事先建立虚拟连接，可以直接给对方地址发送消息。</p><p></p><p>通信方的地址也是由 IP 地址 和 端口号构成。</p><p></p><p>所以相比 TCP 协议，它更加简单快捷。</p><p></p><ol start="2">
<li>没有消息可靠性保证</li>
</ol><p></p><p>UDP 传输的消息如果在网络上丢失了，就丢失了。UDP 协议本身没有重传机制。</p><p></p><p>而 TCP 协议底层有消息验证是否到达，如果丢失，发送方会重传的机制。</p><p></p><p>所以，如果应用要么不在意丢失一些信息，要么应用层自己实现一套机制保证可靠性。</p><p></p><ol start="3">
<li>数据消息发送是独立的报文</li>
</ol><p></p><p>TCP 协议通信双方的信息数据就像流动在管道中，是有明确的先后次序的。</p><p></p><p>发送方应用 先发送的信息肯定 是 先被接收方应用 先接收的 。</p><p></p><p>而 UDP 协议发送的是一个个的独立的报文，接收方应用接收到的次序不一定和发送的次序一致。</p><p></p><p>对于我们应用开发来说，特别需要注意的一点是：系统设计时要确定应用语义中的 <code>最大报文长度</code> 。</p><p></p><p>这样编码的时候，可以确定一个对应长度的 应用程序接收缓冲，防止出现只接收了一部分的情况。</p><p></p><p>TCP Socket 是流式（stream）协议，如果应用接收缓冲不够大，只接受了一部分，没有关系，后面继续接收，然后找到消息边界拼接就可以了。</p><p></p><p>而 UDP 式数据报协议， UDP Socket 如果只接受了数据报的一部分，剩余的消息就会被丢弃。下次接收，只能接收到下一个数据报的内容了。</p><p></p><p><a href="https://stackoverflow.com/a/13317588/2602410" target="_blank">具体参考这里</a></p><p></p><p>根据我<a href="https://stackoverflow.com/a/35697810/2602410" target="_blank">参考的资料</a>，比较合适的报文长度，可以设置为 <code>400个字节</code></p><p></p><h3 id="sr-toc-9">UDP Socket 编程</h3><p></p><p>下面是一个 UDP Socket 通信 的示例代码。</p><p></p><p>实现 客户端 请求服务端返回用户信息 的功能。</p><p></p><p>客户端请求 消息里面的 action 和 name 参数指定了  <code>请求的目的</code>  和  <code>用户名</code></p><p></p><p>客户端代码</p><p></p><pre>import socket,json

BUFF_LEN     = 400                   # 最大报文长度
SERVER_ADDR  = ("127.0.0.1", 18000)  # 指明服务端地址

# 创建 UDP Socket
client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 设置socket超时时间，单位：秒
client_socket.settimeout(2)

# 要发送的信息 对象
message = {
    'action' : '获取信息',
    'name' : '白月黑羽'
} 
# 发送出去的信息必须是字节，所以要先序列化，再编码
sendbytes = json.dumps(message).encode('utf8')
client_socket.sendto(sendbytes, SERVER_ADDR)
try:
    recvbytes, server = client_socket.recvfrom(BUFF_LEN)
    # 接收到的信息是字节，所以要解码，再反序列化
    message = json.loads(recvbytes.decode('utf8'))
    print(message)
except socket.timeout:
    print('接收消息超时')

</pre><p></p><p>服务端代码</p><p></p><pre>import socket,json

BUFF_LEN = 400    # 最大报文长度
ADDR     = ("", 18000)  # 指明服务端地址，IP地址为空表示本机所有IP

# 创建 UDP Socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 绑定地址
server_socket.bind(ADDR)

while True:
    try:
        recvbytes, client_addr = server_socket.recvfrom(BUFF_LEN)
    except socket.timeout:
        continue
    
    print(f'来自 {client_addr} 的请求')

    # 接收到的信息是字节，所以要解码，再反序列化
    message = json.loads(recvbytes.decode('utf8'))
    print(message)
    if message['action'] == '获取信息':
        # 可以从数据库的数据源查询 此用户的信息
        username = message['name']

        # 要发送的信息 对象
        message = {
            'action' : '返回信息',
            'info' : f'{username} 的信息是:xxxxxxxx'
        } 
        # 发送出去的信息必须是字节，所以要先序列化，再编码
        sendbytes = json.dumps(message).encode('utf8')
        server_socket.sendto(sendbytes, client_addr)

</pre><p></p><p>可以看出 UDP 通信服务端 的 socket 也需要绑定端口号。</p><p></p><p>但是和 TCP 不同， 服务端只需要一个 socket 进行通信即可，不需要 2 个 socket 分别用来监听和通信。</p><p></p><p>而 UDP 客户端的 socket 通常不需要指定绑定的端口号， 操作系统会自动帮其选择一个绑定。</p><p></p><p>当不需要使用 UDP Socket 时，可以通过 socket 对象的 close 方法 关闭，如下代码所示。</p><p></p><pre>server_socket.close()

</pre><p></p><p>关闭 socket 后，该端口绑定的 <strong>端口号 就会被释放</strong>，可以再次被本进程或者其它进程的 socket 绑定使用</p><p></p></sr-rd-content>
                            
                            <sr-rd-footer>
                                <sr-rd-footer-group>
                                    <sr-rd-footer-line></sr-rd-footer-line>
                                    <sr-rd-footer-text>全文完</sr-rd-footer-text>
                                    <sr-rd-footer-line></sr-rd-footer-line>
                                </sr-rd-footer-group>
                                <sr-rd-footer-copywrite>
                                    <div>本文由 <a href="http://ksria.com/simpread" target="_blank">简悦 SimpRead</a> 转码，用以提升阅读体验，<a href="https://www.byhy.net/tut/py/etc/socket/" target="_blank">原文地址 </a></div>
                                </sr-rd-footer-copywrite>
                            </sr-rd-footer>
                        </sr-read>
                    </body>
                </html>